datos <- read.csv(file="googleplaystore.csv", header=TRUE, sep=",", stringsAsFactors = FALSE)
num <- c(1:10841)
apps <- c(datos$Category)
number_of_apps <- data.frame(num, apps)
str(datos)
## 'data.frame': 10841 obs. of 13 variables:
## $ App : chr "Photo Editor & Candy Camera & Grid & ScrapBook" "Coloring book moana" "U Launcher Lite â\200“ FREE Live Cool Themes, Hide Apps" "Sketch - Draw & Paint" ...
## $ Category : chr "ART_AND_DESIGN" "ART_AND_DESIGN" "ART_AND_DESIGN" "ART_AND_DESIGN" ...
## $ Rating : num 4.1 3.9 4.7 4.5 4.3 4.4 3.8 4.1 4.4 4.7 ...
## $ Reviews : chr "159" "967" "87510" "215644" ...
## $ Size : chr "19M" "14M" "8.7M" "25M" ...
## $ Installs : chr "10,000+" "500,000+" "5,000,000+" "50,000,000+" ...
## $ Type : chr "Free" "Free" "Free" "Free" ...
## $ Price : chr "0" "0" "0" "0" ...
## $ Content.Rating: chr "Everyone" "Everyone" "Everyone" "Teen" ...
## $ Genres : chr "Art & Design" "Art & Design;Pretend Play" "Art & Design" "Art & Design" ...
## $ Last.Updated : chr "January 7, 2018" "January 15, 2018" "August 1, 2018" "June 8, 2018" ...
## $ Current.Ver : chr "1.0.0" "2.0.0" "1.2.4" "Varies with device" ...
## $ Android.Ver : chr "4.0.3 and up" "4.0.3 and up" "4.0.3 and up" "4.2 and up" ...
El primer paso será hacer un preprocesamiento de cada uno de los atributos con vista a obtener mejores resultados en los algoritmos de minería de datos
Se puede obsrevar que los datos del tamaño de las aplicaciones tienen prefijos métricos (Kilo y Mega). Para poder hacer un análisis de datos efectivo, se eliminarán estos símbolos por sus correspondientes equivalencias numéricas. Además de estos símbolos hay apps cuyo tamaño varían con el dispositivo (“Varies with devices”), estos ejemplos se sustituirán por valores nulos. Por otro lado, también hay instancias que tienen como valor “1000+”, estas se sustituirán por 1000. En resumen, las tareas que se van a realizar son las siguientes:
for (i in 1:length(datos$Size)) {
if(grepl("M",datos$Size[i])){
numero <- as.numeric(gsub("M", "e+06", datos$Size[i]))
datos$Size[i]<- numero
} else if (grepl("k", datos$Size[i])){
numero <- as.numeric(gsub("k", "e+03", datos$Size[i]))
datos$Size[i] <- numero
}else if (grepl("Varies", datos$Size[i])){
datos$Size[i] <- NaN
}else{
datos$Size[i]<-1000
}
}
datos$Size <- as.numeric(datos$Size)
datos$Size[is.na(datos$Size)] <- mean(datos$Size, na.rm=TRUE)
Convertir “Installs” en numeric Removemos el simbolo (+) y luego convertimos a numérico. Comprobamos los cambios.
datos$Installs <- as.numeric(gsub(",", "", gsub("+", "", datos$Installs, fixed = TRUE), fixed=TRUE))
options("scipen"=100, "digits"=4)
str(datos$Installs)
## num [1:10841] 10000 500000 5000000 50000000 100000 50000 50000 1000000 1000000 10000 ...
print(unique(datos$Installs))
## [1] 10000 500000 5000000 50000000 100000 50000
## [7] 1000000 10000000 5000 100000000 1000000000 1000
## [13] 500000000 50 100 500 10 1
## [19] 5 0 NA
##Convertir Nan en 0 porque el Nan viene de una app que es Free
for (i in 1:length(datos$Installs)) {
if(is.na(datos$Installs[i])){
datos$Installs[i]<-0
}
}
Comprobaremos si los valores del atributo “Reviews” son de tipo numérico:
datos$Reviews <- as.numeric(datos$Reviews)
print(sum(is.na(datos$Reviews)))
## [1] 1
Se puede observar que al convertir la columna a número, un valor no se ha podido convertir, ya que no había forma. Daremos un vistazo previo a esta fila:
for (i in 1:length(datos$Reviews)) {
if(is.na(datos$Reviews[i])){
print(i)
print(datos[i,])
datos <- datos[-i,]
}
}
## [1] 10473
## App Category Rating Reviews Size
## 10473 Life Made WI-Fi Touchscreen Photo Frame 1.9 19 NA 1000
## Installs Type Price Content.Rating Genres Last.Updated
## 10473 0 0 Everyone February 11, 2018 1.0.19
## Current.Ver Android.Ver
## 10473 4.0 and up
## [1] 10841
## App Category Rating Reviews Size Installs Type Price Content.Rating
## NA <NA> <NA> NA NA NA NA <NA> <NA> <NA>
## Genres Last.Updated Current.Ver Android.Ver
## NA <NA> <NA> <NA> <NA>
Como solo es una fila, se optará por eliminarla directamente. En la representación de arriba se ve cómo ha desaparecido del dataframe.
Se comprueba que los valores están entre 1 y 5. Tiene valores que son NaN, se sustituyen por la media de la columna
print(range(datos$Rating, na.rm = TRUE))
## [1] 1 5
datos$Rating[is.na(datos$Rating)] <- mean(datos$Rating, na.rm=TRUE)
print(unique(datos$Price))
## [1] "0" "$4.99" "$3.99" "$6.99" "$1.49" "$2.99" "$7.99"
## [8] "$5.99" "$3.49" "$1.99" "$9.99" "$7.49" "$0.99" "$9.00"
## [15] "$5.49" "$10.00" "$24.99" "$11.99" "$79.99" "$16.99" "$14.99"
## [22] "$1.00" "$29.99" "$12.99" "$2.49" "$10.99" "$1.50" "$19.99"
## [29] "$15.99" "$33.99" "$74.99" "$39.99" "$3.95" "$4.49" "$1.70"
## [36] "$8.99" "$2.00" "$3.88" "$25.99" "$399.99" "$17.99" "$400.00"
## [43] "$3.02" "$1.76" "$4.84" "$4.77" "$1.61" "$2.50" "$1.59"
## [50] "$6.49" "$1.29" "$5.00" "$13.99" "$299.99" "$379.99" "$37.99"
## [57] "$18.99" "$389.99" "$19.90" "$8.49" "$1.75" "$14.00" "$4.85"
## [64] "$46.99" "$109.99" "$154.99" "$3.08" "$2.59" "$4.80" "$1.96"
## [71] "$19.40" "$3.90" "$4.59" "$15.46" "$3.04" "$4.29" "$2.60"
## [78] "$3.28" "$4.60" "$28.99" "$2.95" "$2.90" "$1.97" "$200.00"
## [85] "$89.99" "$2.56" "$30.99" "$3.61" "$394.99" "$1.26" "$1.20"
## [92] "$1.04"
Se puede observar que la variable precio tiene un carácter $ que hay que eliminar para poder convertirlo en número. Además, hay varias columnas que tienen valores raros (“Everyone”), estas filas las convertiremos en Nan y posteriormente se sustituirá por la media de precios
for (i in 1:length(datos$Price)){
datos$Price[i] <- as.numeric((gsub("\\$","", datos$Price[i])))
}
datos$Price[is.na(datos$Price)] <- mean(datos$Price, na.rm=TRUE)
datos$Price <- as.numeric(datos$Price)
#Eliminar valores NAN
# for (i in 1:length(datos$Price)){
# if(is.na(datos$Price[i])){
# print(i)
# print(datos[i,])
# datos <- datos[-i,]
#
# }
# }
Lo más curioso es que hay aplicaciones que superan los 350 dólares, tal como se puede ver en el histograma a continuación:
hist(datos$Price)
for (i in 1:length(datos$Price)){
if(datos$Price[i]>350){
print(datos$Price[i])
}
}
## [1] 400
## [1] 400
## [1] 400
## [1] 400
## [1] 400
## [1] 400
## [1] 380
## [1] 400
## [1] 400
## [1] 400
## [1] 400
## [1] 390
## [1] 400
## [1] 400
## [1] 395
## [1] 400
Esta columna tiene algunos datos que están en el formato Category;Subcategory para poder hacer un estudio más exhaustivo, se va a dividir esta columna en dos, por un lado una columna con la Categoría principal y otra con la subcategoría. Luego comprobamos valores unicos.
head(datos$Genres, n = 50)
## [1] "Art & Design" "Art & Design;Pretend Play"
## [3] "Art & Design" "Art & Design"
## [5] "Art & Design;Creativity" "Art & Design"
## [7] "Art & Design" "Art & Design"
## [9] "Art & Design" "Art & Design;Creativity"
## [11] "Art & Design" "Art & Design"
## [13] "Art & Design" "Art & Design"
## [15] "Art & Design" "Art & Design"
## [17] "Art & Design" "Art & Design"
## [19] "Art & Design" "Art & Design"
## [21] "Art & Design" "Art & Design"
## [23] "Art & Design" "Art & Design;Action & Adventure"
## [25] "Art & Design" "Art & Design"
## [27] "Art & Design;Creativity" "Art & Design"
## [29] "Art & Design" "Art & Design"
## [31] "Art & Design" "Art & Design"
## [33] "Art & Design" "Art & Design"
## [35] "Art & Design" "Art & Design"
## [37] "Art & Design;Creativity" "Art & Design"
## [39] "Art & Design" "Art & Design"
## [41] "Art & Design" "Art & Design"
## [43] "Art & Design" "Art & Design;Creativity"
## [45] "Art & Design" "Art & Design"
## [47] "Art & Design" "Art & Design"
## [49] "Art & Design" "Auto & Vehicles"
datos <-separate(data=datos, col = Genres, into = c("Pri_Genre", "Sec_Genre"), sep = ";")
head(datos$Pri_Genre, n = 50)
## [1] "Art & Design" "Art & Design" "Art & Design"
## [4] "Art & Design" "Art & Design" "Art & Design"
## [7] "Art & Design" "Art & Design" "Art & Design"
## [10] "Art & Design" "Art & Design" "Art & Design"
## [13] "Art & Design" "Art & Design" "Art & Design"
## [16] "Art & Design" "Art & Design" "Art & Design"
## [19] "Art & Design" "Art & Design" "Art & Design"
## [22] "Art & Design" "Art & Design" "Art & Design"
## [25] "Art & Design" "Art & Design" "Art & Design"
## [28] "Art & Design" "Art & Design" "Art & Design"
## [31] "Art & Design" "Art & Design" "Art & Design"
## [34] "Art & Design" "Art & Design" "Art & Design"
## [37] "Art & Design" "Art & Design" "Art & Design"
## [40] "Art & Design" "Art & Design" "Art & Design"
## [43] "Art & Design" "Art & Design" "Art & Design"
## [46] "Art & Design" "Art & Design" "Art & Design"
## [49] "Art & Design" "Auto & Vehicles"
head(datos$Sec_Genre)
## [1] NA "Pretend Play" NA NA
## [5] "Creativity" NA
Convertir la fecha que está en formato String a Date
Sys.setlocale("LC_TIME", "C")
## [1] "C"
datos$Last.Updated <- as.Date(datos$Last.Updated, format = "%B %d, %Y",origin='1970-01-01')
Convertir versiones a números con el formato número.número
for (i in 1:length(datos$Current.Ver)){
if(datos$Current.Ver[i]!="Varies with device"){
datos$Current.Ver[i]<-as.numeric(substr(as.character(datos$Current.Ver[i]),0,3))
}
}
Reemplazar los valores nulos con “Varies with device”
for (i in 1:length(datos$Current.Ver)){
if(is.na(datos$Current.Ver[i])){
datos$Current.Ver[i]<-"Varies with device"
}
}
Aqui veremos cuales de las aplicaciones son mas utilizadas por los usuarios. Para esto tenemos que crear un nuevo data frame con la categoria de aplicaciones mas utilizadas.
p <- plot_ly(number_of_apps, labels = ~apps, values = ~num, type = 'pie') %>%
layout(title = 'Aplicaciones mas utilizadas segun categoria',
xaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE),
yaxis = list(showgrid = FALSE, zeroline = FALSE, showticklabels = FALSE))
p
Este tipo de gráficos permite ver si hay alguna relación entre dos o más variables, pudiendo observar si hay una relación directa (cuando una variable crece la otra también) o inversa (cuando una crece, la otra decrece). En el siguiente gráfico se presentará este gráfico para las variables numéricas del conjunto de datos
columnas_numericas = c("Rating","Reviews", "Size", "Installs", "Price")
ggpairs(datos[columnas_numericas],
title="Relations in numeric data")
Los coeficientes de correlación no están cercanos ni a 1 ni a -1, más bien a cero, lo que significa que ninguna variable está a priori relacionada con otra
sprintf("La media de la puntuación es: %f", mean(datos$Rating))
## [1] "La media de la puntuación es: 4.191757"
p<-ggplot(datos, aes(x=Rating)) +
geom_histogram(binwidth = 0.1, fill="red")
p
La media de la calificación es de un 4.17, por lo que en general, los usuarios puntúan muy bien las aplicaciones en la Play Store. Tampoco se ve que haya diferencia de puntuación entre aplicaciones gratuitas y no
sprintf("La media de la puntuación de apps Gratuitas es: %f", mean(datos$Rating[datos$Type=='Free']))
## [1] "La media de la puntuación de apps Gratuitas es: 4.186933"
sprintf("La media de la puntuación de apps de Pago es: %f", mean(datos$Rating[datos$Type!='Free']))
## [1] "La media de la puntuación de apps de Pago es: 4.252223"
g <- ggplot(datos, aes(datos$Category, datos$Rating)) +
geom_violin(scale="width") + theme(axis.text.x = element_text(angle = 85, hjust = 1)) + stat_summary(fun.y=mean, geom="point", shape=15, size=1) + scale_color_brewer(palette = "Dark2") + geom_hline(yintercept = mean(datos$Rating), linetype="dashed", color="red", size = 2)
print(g)
*Con los puntos negros se observa la media para cada categoría, y la línea roja indica la media de todas las apps. Sabiendo esto, las 3 mejores categorías son EDUCATION, EVENTS y ART AND DESIGN las tres peores son DATING, TOOLS y VIDEO PLAYERS
¿Cómo afecta el precio de las aplicaciones a su puntuación?
# library
library(ggplot2)
library(ggExtra)
## Warning: package 'ggExtra' was built under R version 3.5.2
# classic plot :
p=ggplot(datos, aes(x=datos$Price, y=datos$Rating)) +
geom_point() +
theme(legend.position="none")
#+ coord_cartesian(xlim=c(0,10))
# with marginal histogram
p <- ggMarginal(p, type="density")
print(p)
La mayoría de aplicaciones más valoradas se encuentran en el rango de precios de 0 a 50 dólares.
En esta parte intentaremos aplicar varios métodos de minería de datos para poder sacar conclusiones a partir de los datos. A continuación se exponen los algoritmos que se utilizarán y cuál es su propósito: * Regresión Lineal : el objetivo de este algoritmo es predecir un valor continuo numérico (variable dependiente Y) según otras variables (variables independientes Xs). En este caso, es interesante predecir el atributo Rating en función de otros.
En primer lugar haremos el modelo con validación train-test
##https://www.analyticsvidhya.com/blog/2014/12/caret-package-stop-solution-building-predictive-models/
#http://r-statistics.co/Linear-Regression.html
library(dummies)
## Warning: package 'dummies' was built under R version 3.5.2
## dummies-1.5.6 provided by Decision Patterns
library(caret)
## Warning: package 'caret' was built under R version 3.5.2
## Loading required package: lattice
#One hot encoding
datosAu <- dummy.data.frame(datos, names = c("Category", "Type", "Content.Rating", "Pri_Genre"), sep='.')
#Preparar el conjunto de datos eliminando las columnas que no interesan
borrar <- c("App","Category", "Type", "Content.Rating", "Pri_Genre", "Sec_Genre", "Current.Ver", "Android.Ver", "Last.Updated")
datosRegresion <- datosAu[ , !(names(datosAu) %in% borrar)]
#Dividir el conjunto de datos en train y test
set.seed(3456)
trainIndex <- createDataPartition(datosRegresion$Rating, p = .8,
list = FALSE,
times = 1)
datosRegresionTrain <- datosRegresion[ trainIndex,]
datosRegresionTest <- datosRegresion[-trainIndex,]
#Modelo Regresión Lineal Validación Train Test
lmFit<-train(Rating~., data = datosRegresionTrain, method = "lm")
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
pred <- predict(lmFit, datosRegresionTest)
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
modelvalues<-data.frame(obs = datosRegresionTest$Rating, pred=pred)
Validación Train Test. El p-value (p-value: <0.0000000000000002) es mucho menor que el valor de significancia, por lo que los datos son estadísticamente buenos.
summary <- summary(lmFit)
print(defaultSummary(modelvalues))
## RMSE Rsquared MAE
## 0.46321 0.02703 0.31372
Otra cosa interesante que se puede hacer cuando se tiene un modelo de regresión es ver qué variable influye más en la variable que se quiere predecir, en este caso, se observa que el tamaño de la app influye mucho en la puntuación final de la app, así como que la app sea de citas o no en segundo lugar
importancia <- varImp(lmFit)
plot(importancia)
En segundo lugar haremos la validación con K-Cross Validation con k = 10
ctrl<-trainControl(method = "cv",number = 10)
lmCVFit<-train(Rating ~ ., data = datosRegresion, method = "lm", trControl = ctrl, metric="RMSE")
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
## Warning in predict.lm(modelFit, newdata): prediction from a rank-deficient
## fit may be misleading
sum<-summary(lmCVFit)
print(sum$r.squared)
## [1] 0.04064
Se observa que con CV se obtiene un RSquared mayor, lo que significa que el resultado de la predicción es mejor.